﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Base
{
    /// <summary>
    /// 进程监视器类
    /// </summary>
    public class ProcessMonitor : IDisposable
    {
        private readonly string _processName;
        private readonly string _appPath;
        private readonly int _checkIntervalMilliseconds;
        private readonly int _noRespondingMillisecondsTimeout;
        private Process _appProcess = null;
        private readonly ThreadEx _checkThread;
        private readonly object _lock = new object();


        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="appPath">应用程序路径</param>
        /// <param name="checkIntervalMilliseconds">检查间隔,单位:毫秒.当小于0时,外部调用检查函数进行检查</param>
        /// <param name="noRespondingMillisecondsTimeout">未响应超时时长,单位:毫秒.小于等于0取默认值5000</param>
        /// <exception cref="ArgumentException">应用程序不存在</exception>
        public ProcessMonitor(string appPath, int checkIntervalMilliseconds, int noRespondingMillisecondsTimeout = -1)
        {
            if (string.IsNullOrWhiteSpace(appPath))
            {
                throw new ArgumentNullException(nameof(appPath));
            }

            if (noRespondingMillisecondsTimeout <= 0)
            {
                noRespondingMillisecondsTimeout = 5000;
            }

            this._appPath = PathEx.GetFullPath(appPath);
            if (!File.Exists(this._appPath))
            {
                throw new ArgumentException($"被保护程序\"{appPath}\"不存在.");
            }

            this._processName = Path.GetFileNameWithoutExtension(this._appPath);
            this._checkIntervalMilliseconds = checkIntervalMilliseconds;
            this._noRespondingMillisecondsTimeout = noRespondingMillisecondsTimeout;

            if (this.IsInnerCheck())
            {
                this._checkThread = new ThreadEx(this.CheckThreadMethod, "检查线程");
            }

            var procesArr = this.GetCurrentProcessNameProcesss();
            if (procesArr.Length == 1)
            {
                this._appProcess = procesArr[0];
            }
            else
            {
                this.KillAllProcess(procesArr);//清理多余的进程
            }
        }

        private bool IsInnerCheck()
        {
            return this._checkIntervalMilliseconds > 0;
        }

        private Process[] GetCurrentProcessNameProcesss()
        {
            return Process.GetProcessesByName(this._processName).Where(t =>
            {
                try
                {
                    return t.MainModule != null && string.Equals(t.MainModule.FileName, this._appPath, StringComparison.OrdinalIgnoreCase);
                }
                catch
                {
                    return false;
                }
            }).ToArray();
        }

        private void KillAllProcess(Process[] procesArr = null)
        {
            this.KillCurrentProcess();

            //当前进程杀死,那么同名进程一个都不放过
            if (procesArr == null)
            {
                procesArr = this.GetCurrentProcessNameProcesss();
            }

            foreach (var process in procesArr)
            {
                try
                {
                    process.Kill();
                    process.Dispose();
                }
                catch (InvalidOperationException)
                {

                }
            }
        }

        private void KillCurrentProcess()
        {
            if (this._appProcess != null)
            {
                this.RemoveMonitor();
                try
                {
                    this._appProcess.Kill();
                    this._appProcess.Dispose();
                }
                catch (InvalidOperationException)
                {

                }
                this._appProcess = null;
            }
        }


        /// <summary>
        /// 开始监视
        /// </summary>
        public void Start()
        {
            Process[] processes = Process.GetProcessesByName(this._processName).Where(t =>
            {
                try
                {
                    return t.MainModule != null && string.Equals(t.MainModule.FileName, this._appPath, StringComparison.OrdinalIgnoreCase);
                }
                catch
                {
                    return false;
                }
            }).ToArray();


            if (processes.Length == 0)
            {
                this.PrimitiveStart();
            }
            else if (processes.Length == 1)
            {
                this._appProcess = processes[0];
                this.AddMonitor();
            }
            else
            {
                this.KillAllProcess();
                this.PrimitiveStart();
            }

            if (this.IsInnerCheck())
            {
                this._checkThread.Start();
            }
        }

        /// <summary>
        /// 停止监视
        /// </summary>
        public void Stop()
        {
            if (this.IsInnerCheck())
            {
                this._checkThread.Stop();
            }

            this.KillAllProcess();
        }

        private void CheckThreadMethod(ThreadPara threadPara)
        {
            try
            {
                while (!threadPara.Token.IsCancellationRequested)
                {
                    try
                    {
                        if (threadPara.WaitOne(this._checkIntervalMilliseconds))
                        {
                            //收到消息,说明线程停止了
                            continue;
                        }

                        this.Check();
                    }
                    catch (Exception exi)
                    {
                        Console.WriteLine(exi.ToString());
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }


        /// <summary>
        /// 检查进程
        /// </summary>
        public void Check()
        {
            lock (this._lock)
            {
                if (this.ProcessIsEnd())
                {
                    this.KillAllProcess();
                    this.PrimitiveStart();
                }
                else
                {
                    if (this._appProcess.Responding)
                    {
                        this.FreeNoRespondingWatch();
                    }
                    else
                    {
                        if (this._noRespondingWatch == null)
                        {
                            this._noRespondingWatch = Stopwatch.StartNew();
                        }
                        else
                        {
                            if (this._noRespondingWatch.ElapsedMilliseconds > this._noRespondingMillisecondsTimeout)
                            {
                                this.RemoveMonitor();
                                this.KillProcess();
                                this.PrimitiveStart();
                            }
                        }
                    }
                }
            }
        }


        private bool ProcessIsEnd()
        {
            var process = this._appProcess;
            if (process != null && !process.HasExited)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        private Stopwatch _noRespondingWatch = null;
        private void FreeNoRespondingWatch()
        {
            var noRespondingWatch = this._noRespondingWatch;
            if (noRespondingWatch != null)
            {
                noRespondingWatch.Stop();
                this._noRespondingWatch = null;
            }
        }

        private void PrimitiveStart()
        {
            this.FreeNoRespondingWatch();
            var startInfo = new ProcessStartInfo();
            startInfo.UseShellExecute = true;
            startInfo.FileName = this._appPath;
            startInfo.Arguments = Process.GetCurrentProcess().Id.ToString();
            startInfo.WorkingDirectory = Path.GetDirectoryName(this._appPath);
            startInfo.Verb = "runas";
            this._appProcess = Process.Start(startInfo);
            this.AddMonitor();
        }

        private void AddMonitor()
        {
            if (this._appProcess != null)
            {
                this._appProcess.EnableRaisingEvents = true;
                this._appProcess.Exited += Process_Exited;
            }
        }

        private void RemoveMonitor()
        {
            if (this._appProcess != null)
            {
                this._appProcess.EnableRaisingEvents = false;
                this._appProcess.Exited -= Process_Exited;
            }
        }

        private void Process_Exited(object sender, EventArgs e)
        {
            try
            {
                lock (this._lock)
                {
                    ZLoger.Info($"程序[{this._appPath}]停止,正在启动...");
                    this.RemoveMonitor();
                    this.KillProcess();
                    this.PrimitiveStart();
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex, $"启动程序[{this._appPath}]异常");
            }
        }

        private void KillProcess()
        {
            if (this._appProcess == null)
            {
                return;
            }

            try
            {
                this._appProcess.Kill();
            }
            catch (InvalidOperationException)
            {

            }
            this._appProcess = null;
        }

        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            if (this.IsInnerCheck())
            {
                this._checkThread.Stop();
                this._checkThread.Dispose();
            }

            this.KillAllProcess();
        }
    }
}
